'''
Contains the functions needed to create a Dell self-dispatch support request,
as well as to resubmit a denied request.
'''
from json import dumps as json_dumps, loads as json_loads
from json.decoder import JSONDecodeError
from requests import post as requests_post
from xmltodict import parse as xmltodict_parse
from pyperclip import copy as pyperclip_copy
from dateparser import parse as dateparser_parse
import PySimpleGUI as sg
from openpyxl import Workbook as OpenpyxlWorkbook
from warranty_manager.claim.common import (
    TEXT_LABEL_SIZE, SUBMIT_COLOR, CANCEL_COLOR, get_file_path
)
from warranty_manager.gui.confirmation import get_confirmation
from warranty_manager.claim.dell.common import get_error_message
from warranty_manager.claim.dell.warranty_api import get_model_name


def get_part_number_from_description(part_nums_dict=None):
    '''Gets the correct Dell part number from its product description.'''
    if part_nums_dict:
        part_num = ppid = None

        description_list = list( part_nums_dict.keys() )
        description_list.sort()

        layout = [
            [
                sg.Text( 'Part Number', size=(14, 1) ),
                sg.InputCombo(tuple(description_list), key='-DESC-')
            ],
            [
                sg.Text(
                    'PPID (Optional)',
                    size=(14, 1),
                    tooltip="A 20 character code on a sticker attached to all Dell parts,\n"
                    "and is required to order LCD panels, batteries, and AC adapters.\n"
                    "It can be displayed on one row, or separated with spaces or dashes.\n"
                    "The spaces/dashes should be omitted for Self-Dispatch support requests."
                ),
                sg.Input(key='-PPID-', enable_events=True)
            ],
            [
                sg.Button('Select Part', button_color=SUBMIT_COLOR),
                sg.Cancel(button_color=CANCEL_COLOR)
            ]
        ]

        window = sg.Window('Dispatch API Parts', layout)

        while True:
            event, values = window.read()

            if event in (None, 'Cancel'):
                break
            elif event == '-PPID-':
                ppid = values['-PPID-'].strip("-").strip().upper()
                window['-PPID-'].update(ppid)

                if len(ppid) > 20:
                    sg.popup_error('The PPID cannot be longer than 20 characters!', title='Error')
                    window['-PPID-'].update(ppid[:20])

            elif event == 'Select Part':
                part_num = part_nums_dict.get(values['-DESC-'])
                ppid = values['-PPID-']

                if not part_num:
                    sg.popup_error('You must enter a part number!', title='Error')
                elif not ppid \
                and get_confirmation('add the specified part number with no PPID'):
                    break
                elif ppid:
                    sg.popup(
                        f'Part number "{part_num }" and its PPID "{ppid}" '
                        'have been added to the request!', title='Success'
                    )
                    break
                else:
                    continue

            else:
                continue

        window.close()
        return (part_num, ppid)


def get_part_numbers_dictionary():
    '''Gets the part number for a dell device, using its product description.'''
    json_path = get_file_path('Dell Parts JSON')

    if json_path:
        with open(json_path, 'r') as json_reader:
            try:
                # A dictionary of the part descriptions/numbers
                return json_loads( json_reader.read() )
            except (JSONDecodeError, UnicodeDecodeError):
                sg.popup_error(
                    'You must select a JSON file for the part numbers dictionary!',
                    title='Error'
                )

    return {}


def get_customer(relationships):
    '''
    Gets a customer name from a list of customer names, based on the
    relationships found using the CheckLogin method of the Dispatch API.
    '''
    branch = customer_name = track = None

    try:
        customer_names = list(
            map(lambda relationship: relationship['CustomerName'], relationships)
        )
    except TypeError: # check_login window was closed
        return (branch, customer_name, track)

    prompt = 'Which customer did you want to choose?'
    prompt_length = len(prompt)

    layout = [
        [sg.Text(prompt)],
        [sg.Combo( customer_names, key='-CUSTOMER-', size=(prompt_length, 1) )],
        [
            sg.Button('Select Customer', button_color=SUBMIT_COLOR),
            sg.Cancel(button_color=CANCEL_COLOR)
        ]
    ]

    window = sg.Window('Dispatch API Customer', layout)

    while True:
        event, values = window.read()

        if event in (None, 'Cancel'):
            break
        elif event == 'Select Customer':
            if not values['-CUSTOMER-']:
                sg.popup_error('You must select a customer name!', title='Error')
            elif values['-CUSTOMER-'] in customer_names:
                for relationship in relationships:
                    if values['-CUSTOMER-'] == relationship['CustomerName']:
                        branch = relationship['BranchName']
                        customer_name = values['-CUSTOMER-']
                        track = relationship['Track']
                        break

                break
            else:
                continue
        else:
            continue

    window.close()
    return (branch, customer_name, track)


def get_address():
    '''
    Gets all the address information for the self-dispatch support request,
    for the sandbox requests, as well as if an address is not in the user's
    TechDirect address book.
    '''
    address = {}

    layout = [
        [sg.T('Country ISO Code', size=TEXT_LABEL_SIZE), sg.I(key='-ISO-')],
        [sg.T('City', size=TEXT_LABEL_SIZE), sg.I(key='-CITY-')],
        [sg.T('State', size=TEXT_LABEL_SIZE), sg.I(key='-STATE-')],
        [sg.T('Zip Code', size=TEXT_LABEL_SIZE), sg.I(key='-ZIP-')],
        [sg.T('Address Line 1', size=TEXT_LABEL_SIZE), sg.I(key='-ADDRESS_1-')],
        [
            sg.T(
                'Address Line 2',
                size=TEXT_LABEL_SIZE,
                tooltip='Required for an apartment number, suite number, etc.'
            ),
            sg.I(key='-ADDRESS_2-')
        ],
        [sg.T('Time Zone', size=TEXT_LABEL_SIZE), sg.I(key='-ZONE-')],
        [sg.Submit(button_color=SUBMIT_COLOR), sg.Cancel(button_color=CANCEL_COLOR)]
    ]

    window = sg.Window('Address Information', layout)

    while True:
        event, values = window.read()

        if event in (None, 'Cancel'):
            break
        elif event == 'Submit':

            if not values['-ISO-'].strip():
                sg.popup_error("You must enter a country's ISO code!", title='Error')
                continue
            else:
                address['iso'] = values['-ISO-'].strip()

            if not values['-CITY-'].strip():
                sg.popup_error("You must enter a city!", title='Error')
                continue
            else:
                address['city'] = values['-CITY-'].strip()

            if not values['-STATE-'].strip():
                sg.popup_error("You must enter a state!", title='Error')
                continue
            else:
                address['state'] = values['-STATE-'].strip()

            if not values['-ZIP-'].strip():
                sg.popup_error("You must enter a zip code!", title='Error')
                continue
            else:
                try:
                    int( values['-ZIP-'].strip() )
                except ValueError:
                    sg.popup_error('You must enter a number for the zip code!', title='Error')
                    continue
                else:

                    if len( values['-ZIP-'].strip() ) != 5:
                        sg.popup_error('The zip code must be five digits long!', title='Error')
                        continue
                    else:
                        address['zip'] = values['-ZIP-'].strip()

            if not values['-ADDRESS_1-'].strip():
                sg.popup_error("You must enter a line one address!", title='Error')
                continue
            else:
                address['line 1'] = values['-ADDRESS_1-'].strip()

            # The address line 2 is optional
            if values['-ADDRESS_2-'].strip():
                address['line 2'] = values['-ADDRESS_2-'].strip()
            else:
                address['line 2'] = ''

            if not values['-ZONE-'].strip():
                sg.popup_error("You must enter a time zone!", title='Error')
                continue
            else:
                address['zone'] = values['-ZONE-'].strip()

            break
        else:
            continue

    window.close()
    return address


def get_dispatch_headers(token, method_name):
    '''Gets the correct HTTP headers for all Dell Dispatch API requests.'''
    return {
        'Authorization': f'Bearer {token}',
        'Content-Type': 'text/xml;charset=UTF-8',
        'SOAPAction': f'http://api.dell.com/IDispatchService/{method_name}',
    }


def get_url(sandbox=False):
    '''
    Gets either the sandbox or production URL for the Dell Dispatch API.
    '''
    if sandbox is True:
        return "https://apigtwb2cnp.us.dell.com/Sandbox/support/dispatch/v3/service"

    return "https://apigtwb2c.us.dell.com/PROD/support/dispatch/v3/service"


def check_user(un, pw, token, sandbox=False):
    '''
    Determines whether or not a Dell TechDirect user's account is active,
    using the CheckUser method of the Dispatch API; this is required to create
    a new support request, using the CreateDispatch method of the Dispatch API.
    '''
    data = f'''
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:api="http://api.dell.com">
        <soapenv:Header/>
        <soapenv:Body>
            <api:CheckUser>
                <api:CheckUserRequest>
                    <api:Login>{un}</api:Login>
                    <api:Password>{pw}</api:Password>
                </api:CheckUserRequest>
            </api:CheckUser>
        </soapenv:Body>
    </soapenv:Envelope>
    '''

    res = requests_post(
        url=get_url(sandbox=sandbox),
        data=data,
        headers=get_dispatch_headers(token, 'CheckUser')
    )

    res_xml = xmltodict_parse(res.text)

    try:
        full_name = res_xml['soap:Envelope']['soap:Body']['CheckUserResponse']\
        ['CheckUserResult']['LoginResult']['FullName']

        inactive = res_xml['soap:Envelope']['soap:Body']['CheckUserResponse']\
        ['CheckUserResult']['LoginResult']['Inactive']

        locked = res_xml['soap:Envelope']['soap:Body']['CheckUserResponse']\
        ['CheckUserResult']['LoginResult']['Locked']
    except KeyError:
        sg.popup_error(get_error_message(res_xml), title='Error')
    else:
        if inactive == 'false':
            inactive = False
        else:
            inactive = True

        if locked == 'false':
            locked = False
        else:
            locked = True

        return (full_name, inactive, locked)


def check_login(un, pw, token, sandbox=False):
    '''
    Gets the customer name, branch, and track, using the CheckLogin method
    of the Dispatch API; these are required to create a new support request,
    using the CreateDispatch method of the Dispatch API.
    '''
    data = f'''
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:api="http://api.dell.com">
        <soapenv:Header/>
        <soapenv:Body>
            <api:CheckLogin>
                <api:CheckLoginRequest>
                    <api:Login>{un}</api:Login>
                    <api:Password>{pw}</api:Password>
                </api:CheckLoginRequest>
            </api:CheckLogin>
       </soapenv:Body>
    </soapenv:Envelope>
    '''

    res = requests_post(
        url=get_url(sandbox=sandbox),
        data=data,
        headers=get_dispatch_headers(token, 'CheckLogin')
    )

    res_xml = xmltodict_parse(res.text)

    try:
        name = res_xml['soap:Envelope']['soap:Body']['CheckLoginResponse']\
        ['CheckLoginResult']['LoginResult']['FullName']

        relationships = res_xml['soap:Envelope']['soap:Body']['CheckLoginResponse']\
        ['CheckLoginResult']['LoginResult']['Relationships']['RelationshipInfo']
    except KeyError:
        sg.popup_error(get_error_message(res_xml), title='Error')
        return (None, None)
    else:
        return (name, relationships)


def create_dispatch(un, pw, token, request_info, sandbox=False):
    '''
    Creates a new self-dispatch support request, using the CreateDispatch method
    of the Dell Dispatch API.
    '''
    name, relationships = check_login(un, pw, token, sandbox=sandbox)
    branch, customer, track = get_customer(relationships)

    if name and relationships and branch and customer and track:
        parts_string = ''

        for part in request_info['parts']:
            if part['PPID']:
                parts_string += f'''
                    <tem:PartInfo>
                        <tem:PartNumber>{part['PN']}</tem:PartNumber>
                        <tem:PPID>{part['PPID']}</tem:PPID>
                        <tem:Quanitity>1</tem:Quanitity>
                    </tem:PartInfo>
                '''
            else:
                parts_string += f'''
                    <tem:PartInfo>
                        <tem:PartNumber>{part['PN']}</tem:PartNumber>
                        <tem:PPID/>
                        <tem:Quanitity>1</tem:Quanitity>
                    </tem:PartInfo>
                '''

        # To manually add the address information to the request
        if request_info['address']['name']:
            address_string = f"<tem:AddressBookName>{request_info['address']['name']}</tem:AddressBookName>"
        else:
            address_string = f'''
            <tem:CountryISOCode>{request_info['address']['iso']}</tem:CountryISOCode>
            <tem:City>{request_info['address']['city']}</tem:City>
            <tem:State>{request_info['address']['state']}</tem:State>
            <tem:ZipPostalCode>{request_info['address']['zip']}</tem:ZipPostalCode>
            <tem:AddressLine1>{request_info['address']['line 1']}</tem:AddressLine1>
            <tem:AddressLine2>{request_info['address']['line 2']}</tem:AddressLine2>
            <tem:AddressLine3/>
            <tem:TimeZone>{request_info['address']['zone']}</tem:TimeZone>
            '''

        data = f"""
        <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns:tem="http://api.dell.com">
            <soapenv:Header/>
            <soapenv:Body>
                <tem:CreateDispatch>
                    <tem:CreateDispatchRequest>
                        <tem:Login>{un}</tem:Login>
                        <tem:Password>{pw}</tem:Password>
                        <tem:Dispatch>
                            <tem:TechEmail>{un}</tem:TechEmail>
                            <tem:Branch>{branch}</tem:Branch>
                            <tem:Customer>{customer}</tem:Customer>
                            <tem:Track>{track}</tem:Track>
                            <tem:ServiceTag>{request_info['tag']}</tem:ServiceTag>
                            <tem:PrimaryContactName>{name}</tem:PrimaryContactName>
                            <tem:PrimaryContactPhone>{request_info['phone']}</tem:PrimaryContactPhone>
                            <tem:PrimaryContactEmail>{un}</tem:PrimaryContactEmail>
                            <tem:ShipToAddress>{address_string}</tem:ShipToAddress>
                            <tem:RequestCompleteCare>{request_info['adp']}</tem:RequestCompleteCare>
                            <tem:RequestReturnToDepot>{request_info['drop ship']}</tem:RequestReturnToDepot>
                            <tem:RequestOnSiteTechnician>false</tem:RequestOnSiteTechnician>
                            <tem:ReferencePONumber>{request_info['po']}</tem:ReferencePONumber>
                            <tem:TroubleshootingNote>{request_info['note']}</tem:TroubleshootingNote>
                            <tem:OverrideDPSType/>
                            <tem:Parts>{parts_string}</tem:Parts>
                            <tem:Attachments/>
                        </tem:Dispatch>
                    </tem:CreateDispatchRequest>
                </tem:CreateDispatch>
            </soapenv:Body>
        </soapenv:Envelope>
        """

        res = requests_post(
            url=get_url(sandbox=sandbox),
            data=data,
            headers=get_dispatch_headers(token, 'CreateDispatch')
        )

        res_xml = xmltodict_parse(res.text)

        try:
            dispatch_create_result = res_xml['soap:Envelope']\
            ['soap:Body']['CreateDispatchResponse']\
            ['CreateDispatchResult']['DispatchCreateResult']
        except KeyError:
            sg.popup_error(get_error_message(res_xml), title='Error')
        else:
            dispatch_code = dispatch_create_result.get('DispatchCode')

            if dispatch_code:
                pyperclip_copy(f'Dispatch Code: {dispatch_code}')
                sg.popup(
                    f'Dispatch Code: {dispatch_code}',
                    title='Success', keep_on_top=True
                )
                return dispatch_code
            else:
                notes = dispatch_create_result['Notes']['string']

                # Notes can have more than one string
                if isinstance(notes, list):
                    sg.popup_error('. '.join(notes), title='Error Notes')
                else:
                    sg.popup_error(notes, title='Error Notes')


def get_dispatch_status(un, pw, token, dispatch_code, sandbox=False):
    '''
    Gets a message about whether the specified self-dispatch support request
    were accepted or denied by Dell, using the GetDispatchStatus method of the
    Dispatch API.
    '''
    data = f'''
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:api="http://api.dell.com">
        <soapenv:Header/>
        <soapenv:Body>
            <api:GetDispatchStatus>
                <api:GetDispatchStatusRequest>
                    <api:Login>{un}</api:Login>
                    <api:Password>{pw}</api:Password>
                    <api:DispatchCode>{dispatch_code}</api:DispatchCode>
                </api:GetDispatchStatusRequest>
            </api:GetDispatchStatus>
        </soapenv:Body>
    </soapenv:Envelope>
    '''

    res = requests_post(
        url=get_url(sandbox=sandbox),
        data=data,
        headers=get_dispatch_headers(token, 'GetDispatchStatus')
    )

    res_xml = xmltodict_parse(res.text)

    try:
        dispatch_status_result = res_xml['soap:Envelope']['soap:Body']\
        ['GetDispatchStatusResponse']['GetDispatchStatusResult']\
        ['DispatchStatusResult']
    except KeyError:
        sg.popup_error(get_error_message(res_xml), title='Error')
    else:
        result = dispatch_status_result.get('Result')
        status = dispatch_status_result.get('Status')

        if result:
            sg.popup(
                f"Dispatch Result: {result}",
                title='Info'
            )
        elif status in ('DSP', 'Issued', 'QUE', 'ORD'):
            sg.popup(
                f'Dispatch request {dispatch_code} has been approved by Dell!',
                title='Success'
            )
        elif status == 'Dispatch Denied':
            sg.popup_error(
                "Dispatch has been denied for the following reason: "
                f"{dispatch_status_result['OrderDeniedReason']}.",
                title='Error'
            )
        else:
            sg.popup(
                f"Dispatch Status: {status}",
                title='Status'
            )


def bulk_dispatches_inquiry(un, pw, token, date, sandbox=False):
    '''
    Gets status information for all self-dispatch support requests created
    from the provided date, using the BulkDispatchesInquiry method of the
    Dispatch API.
    '''
    # To make sure the date is valid, and in the correct request format
    created_from = dateparser_parse(date).strftime('%Y-%m-%d')

    if created_from:
        data = f'''
        <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns:api="http://api.dell.com">
           <soapenv:Header/>
           <soapenv:Body>
              <api:BulkDispatchesInquiry>
                 <api:BulkDispatchesInquiryRequest>
                    <api:Login>{un}</api:Login>
                    <api:Password>{pw}</api:Password>
                    <api:CreatedFromDate>{created_from}</api:CreatedFromDate>
                    <!--A list of statuses used to limit the results returned-->
                    <api:InStatuses/>
                    <api:Scope>All</api:Scope>
                 </api:BulkDispatchesInquiryRequest>
              </api:BulkDispatchesInquiry>
           </soapenv:Body>
        </soapenv:Envelope>
        '''

        res = requests_post(
            url=get_url(sandbox=sandbox),
            data=data,
            headers=get_dispatch_headers(token, 'BulkDispatchesInquiry')
        )

        res_xml = xmltodict_parse(res.text)

        try:
            dispatch_inquiry_result = res_xml['soap:Envelope']['soap:Body']\
            ['BulkDispatchesInquiryResponse']['BulkDispatchesInquiryResult']\
            ['DispatchInquiryResult']['DispatchInquiryResult']
        except KeyError:
            sg.popup_error(get_error_message(res_xml), title='Error')
        else:
            wb = OpenpyxlWorkbook()
            ws = wb.active
            ws.title = "Dell Self-Dispatch Requests"
            ws.append(['Dispatch Code', 'Dispatch Number', 'Dispatch Status'])

            for inquiry in dispatch_inquiry_result:
                ws.append([
                    inquiry['Code'],
                    inquiry['DellDispatchNumber'],
                    inquiry['Status']
                ])

            wb.save(f'Dell Self-Dispatch Requests Created from {created_from}.xlsx')
            sg.popup('Bulk Dispatch Inquiry workbook as been created!', title='Success')


def resubmit_dispatch(un, pw, token, request_info, sandbox=False):
    '''
    Resubmits a denied self-dispatch support request, using the ResubmitDispatch
    method of the Dell Dispatch API.
    '''
    parts_string = ''

    for part in request_info['parts']:
        if part['PPID']:
            parts_string += f'''
            <api:PartInfo>
                <api:PartNumber>{part['PN']}</api:PartNumber>
                <api:PPID>{part['PPID']}</api:PPID>
                <api:Quanitity>1</api:Quanitity>
            </api:PartInfo>
            '''
        else:
            parts_string += f'''
            <api:PartInfo>
                <api:PartNumber>{part['PN']}</api:PartNumber>
                <api:PPID/>
                <api:Quanitity>1</api:Quanitity>
            </api:PartInfo>
            '''

    data = f'''
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:api="http://api.dell.com">
        <soapenv:Header/>
        <soapenv:Body>
            <api:ResubmitDispatch>
                <api:ResubmitDispatchRequest>
                    <api:Login>{un}</api:Login>
                    <api:Password>{pw}</api:Password>
                    <api:Dispatch>
                        <api:DispatchCode>{request_info['dispatch code']}</api:DispatchCode>
                        <api:TroubleshootingNote>{request_info['note']}</api:TroubleshootingNote>
                        <api:Parts>{parts_string}</api:Parts>
                    </api:Dispatch>
                </api:ResubmitDispatchRequest>
            </api:ResubmitDispatch>
        </soapenv:Body>
    </soapenv:Envelope>
    '''

    res = requests_post(
        url=get_url(sandbox=sandbox),
        data=data,
        headers=get_dispatch_headers(token, 'ResubmitDispatch')
    )

    res_xml = xmltodict_parse(res.text)

    try:
        dispatch_code = res_xml['soap:Envelope']['soap:Body']\
        ['ResubmitDispatchResponse']['ResubmitDispatchResult']\
        ['DispatchCreateResult']['DispatchCode']
    except KeyError:

        try:
            notes = res_xml['soap:Envelope']['soap:Body']\
            ['ResubmitDispatchResponse']['ResubmitDispatchResult']\
            ['DispatchCreateResult']['Notes']['string']
        except KeyError:
            sg.popup(get_error_message(res_xml), title='Error')
        else:
            sg.popup_error(f'Notes: {notes}', title='Error')

    else:
        status = res_xml['soap:Envelope']['soap:Body']\
        ['ResubmitDispatchResponse']['ResubmitDispatchResult']\
        ['DispatchCreateResult']['Status']
        sg.popup(f'Dispatch Code: {dispatch_code}\nStatus: {status}', title='Info')


def get_parts_by_service_tag(un, pw, token, tag, sandbox=False):
    '''
    Gets every part number and description for the provided Dell service tag,
    and saves it to a JSON file, using the GetPartsbyServiceTag method of the
    Dell Dispatch API.
    '''
    data = f"""
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:api="http://api.dell.com">
        <soapenv:Header/>
        <soapenv:Body>
            <api:GetPartsbyServiceTag>
                <api:GetPartsbyServiceTag>
                    <api:Login>{un}</api:Login>
                    <api:Password>{pw}</api:Password>
                    <api:ServiceTag>{tag}</api:ServiceTag>
                </api:GetPartsbyServiceTag>
            </api:GetPartsbyServiceTag>
        </soapenv:Body>
    </soapenv:Envelope>
    """

    res = requests_post(
        url=get_url(sandbox=sandbox),
        data=data,
        headers=get_dispatch_headers(token, 'GetPartsbyServiceTag')
    )

    res_xml = xmltodict_parse(res.text)

    try:
        parts_list = res_xml['soap:Envelope']['soap:Body']\
        ['GetPartsbyServiceTagResponse']['GetPartsbyServiceTagResult']\
        ['PartsbyTagResult']['Parts']['PartInformation']
    except KeyError:
        sg.popup_error(get_error_message(res_xml), title='Error')
    else:
        # To put the model name into the JSON file name
        model = get_model_name(tag)

        if model:
            # To contain each part's description and number
            parts_dict = {}

            for part_info in parts_list:
                parts_dict[ part_info['PartDescription'] ] = part_info['PartNumber']

            with open(f'{model} part numbers.json', 'w') as json_writer:
                json_writer.write( json_dumps(parts_dict) )

            sg.popup(f'The JSON file for {model} has been created!', title='Success')


def get_parts_by_device_type(un, pw, token, device_type, sandbox=False):
    '''
    Gets every part number and description for the provided Dell device type,
    and saves it to a JSON file, using the GetPartsbyModel method of the
    Dell Dispatch API.
    '''
    device_title = device_type.title()

    data = f'''
    <soapenv:Envelope
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:api="http://api.dell.com">
       <soapenv:Header/>
       <soapenv:Body>
          <api:GetPartsbyModel>
             <api:GetPartsbyModelRequest>
               <api:Login>{un}</api:Login>
                <api:Password>{pw}</api:Password>
                <api:ModelCode>{device_title}</api:ModelCode>
             </api:GetPartsbyModelRequest>
          </api:GetPartsbyModel>
       </soapenv:Body>
    </soapenv:Envelope>
    '''

    res = requests_post(
        url=get_url(sandbox=sandbox),
        data=data,
        headers=get_dispatch_headers(token, 'GetPartsbyModel')
    )

    res_xml = xmltodict_parse(res.text)

    try:
        parts_list = res_xml['soap:Envelope']['soap:Body']\
        ['GetPartsbyModelResponse']['GetPartsbyModelResult']\
        ['PartsbyModelResult']['Parts']['PartInformation']
    except KeyError:
        sg.popup_error(get_error_message(res_xml), title='Error')
    else:
        # To contain each part's description and number
        parts_dict = {}

        for part_info in parts_list:
            parts_dict[ part_info['PartDescription'] ] = part_info['PartNumber']

        with open(f'{device_title} part numbers.json', 'w') as json_writer:
            json_writer.write( json_dumps(parts_dict) )

        sg.popup(f'The JSON file for {device_title} has been created!', title='Success')
